package cheme.ProcessModel.ProcessUnits.Streams;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.geometry.Point;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import cheme.ProcessModel.AbstractParent;
import cheme.ProcessModel.ChemePropertyChangeEvent;
import cheme.ProcessModel.IChild;
import cheme.ProcessModel.IModelElement;
import cheme.ProcessModel.IPropertyAware;
import cheme.ProcessModel.ModelEnv;
import cheme.ProcessModel.ProcessModelConstants;
import cheme.ProcessModel.Interpreter.ModelFactory;
import cheme.ProcessModel.ProcessUnits.ITerminal;
import cheme.ProcessModel.ProcessUnits.ProcessUnit;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.IGeneralTagComponent;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.OverallTagComponent;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.Tag;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.TagComponent;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.TagComponentList;


public abstract class AbstractStream extends AbstractParent<Tag> implements IStream, Serializable
{
	/*
	 * Serialization version identifier. (Set to default for now)
	 */
	
	private static final long serialVersionUID = 1L;
	
	/*
	 * The source of a stream is considered its parent for hierarchy use. The source 
	 * value is stored in the streams parent field, accessible via get/set parent methods. 
	 */
	
	/*
	 * The sink/destination for this stream.
	 */
	private ProcessUnit outPU;
	
	/*
	 * Simple list of bendpoints in the stream.
	 * (In the future it would be nice to have these translate with the stream's endpoints.
	 * By default, they do not.)
	 */
	private List<Point> bendpoints = new ArrayList<Point>();
	
	private boolean terminalStream = false;
	
	public AbstractStream(IModelElement parent)
	{
		super(parent);
		
		Tag tag = (Tag)ModelEnv.getInstanceFor(this).createModelElement(ProcessModelConstants.TAG, this);
		add(tag, TAG_INDEX);
		tag.initialize();
	}
	
	public AbstractStream(IModelElement parent, Element xmlDoc)
	{
		super(xmlDoc);
		this.parent = parent;
		Tag tag = (Tag)ModelEnv.getInstanceFor(this).createModelElement(ProcessModelConstants.TAG, this);
		add(tag, TAG_INDEX);
		tag.initializeXml();
		
		//get & set the overall tag
		Element children = (Element)xmlDoc.getElementsByTagName("Children").item(0);
		Element overallTagXml = (Element)children.getElementsByTagName("TagComponent").item(0);
		NodeList tagComponents = overallTagXml.getElementsByTagName("Components").item(0).getChildNodes();
		OverallTagComponent overallTag = (OverallTagComponent)tag.get(Tag.OVERALL_INDEX);
		
		//loop through the xml children, examining the "type" attribute to determine
		//where to put things
		for(int i = 0; i < tagComponents.getLength(); i++)
		{
			Element currentElement = (Element)tagComponents.item(i);
			Element text = (Element)currentElement.getElementsByTagName("Text").item(0);
			if(currentElement.getAttribute("type").compareTo(ProcessModelConstants.VARIABLE_LABEL) == 0)
			{
				overallTag.setLabel(text.getFirstChild().getNodeValue());
			}
			else if(currentElement.getAttribute("type").compareTo(ProcessModelConstants.VARIABLE_QUANTITY) == 0)
			{
				overallTag.setQuantity(text.getFirstChild().getNodeValue());
			}
			else if(currentElement.getAttribute("type").compareTo(ProcessModelConstants.VARIABLE_UNITS) == 0)
			{
				overallTag.setUnits(text.getFirstChild().getNodeValue());
			}
		}		
		
		//now, add individual rows to the tag component list
		Element tagComponentList = (Element)children.getElementsByTagName("TagComponentList").item(0);
		Element tagComponentChildren = (Element)tagComponentList.getElementsByTagName("Children").item(0);
		tagComponents = tagComponentChildren.getChildNodes();
		for(int i = 0; i < tagComponents.getLength(); i++)
		{
			TagComponent t = new TagComponent(parent);
			Element currentElement = (Element)tagComponents.item(i);
			Element currentElementComponents = (Element)currentElement.getElementsByTagName("Components").item(0);
			NodeList nodes = currentElementComponents.getChildNodes();
			
			for(int j = 0; j < nodes.getLength(); j++)
			{
				Element currentTagComponent = (Element)nodes.item(j);
				Element text = (Element)currentTagComponent.getElementsByTagName("Text").item(0);
				if(currentTagComponent.getAttribute("type").compareTo(ProcessModelConstants.VARIABLE_LABEL) == 0)
				{
					t.setLabel(text.getFirstChild().getNodeValue());
				}
				else if(currentTagComponent.getAttribute("type").compareTo(ProcessModelConstants.VARIABLE_QUANTITY) == 0)
				{
					t.setQuantity(text.getFirstChild().getNodeValue());
				}
				else if(currentTagComponent.getAttribute("type").compareTo(ProcessModelConstants.VARIABLE_UNITS) == 0)
				{
					t.setUnits(text.getFirstChild().getNodeValue());
				}
				else if(currentTagComponent.getAttribute("type").compareTo(ProcessModelConstants.VARIABLE_MATERIAL) == 0)
				{
					t.setMaterial(text.getFirstChild().getNodeValue());
				}
			}
			TagComponentList tcl = (TagComponentList)tag.get(Tag.LIST_INDEX);
			tcl.add(t);
			
			if(t.getMaterial().toLowerCase().compareTo("select") != 0)
			{
				t.setInitial(false);
			}
		}
		
		//add an empty row at the bottom of the table
		//tag.addBlankRow();
	}
	
	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
	{
		//System.out.println("AbstractStream . readObject -- start");
		/*
		 * Standard disclaimer: the JAVA  property listeners are not serializable and ...
		 */
		ModelEnv.getInstanceFor(this).hookModelElement(this);
		
		//System.out.println("AbstractStream . readObject -- end");
	}
	
	/*
	 * Note that this is intercepting the fireing of events due to adding or removing equation tokens, 
	 * this method does not respond to events from the child elements such as text change.
	 */
	@Override
	public void firePropertyChange(ChemePropertyChangeEvent evt)
	{
		super.firePropertyChange(evt);
		
		if(evt.getPropertyName().equals(IPropertyAware.P_CHILD) || //change in children set membership--common, this one.
			evt.getPropertyName().equals(IPropertyAware.P_CHILDREN))//ditto
		{
			super.firePropertyChange(
					new ChemePropertyChangeEvent(this, P_INTERPRETABLE, evt.getOldValue(), evt.getNewValue(), evt));
		}
	}
	
	/*
	 * -------------------------------------------------------------------------
	 * Fulfills the basics of the IStream interface-which defines the 'edge'
	 * mechanics of the node-link aspects of the visual.
	 */

	public boolean isTerminalStream()
	{
		return terminalStream;
	}

	public void setTerminalStream(boolean terminalStream)
	{
		this.terminalStream = terminalStream;
	}

	public void attachSource()
	{
		if(!getSource().getOutStreams().contains(this))
		{
			getSource().addOutStream(this);
		}
	}

	public void attachSink()
	{
		if(!getSink().getInStreams().contains(this))
		{
			getSink().addInStream(this);
		}
	}

	public void detachSource()
	{
		getSource().removeOutStream(this);
	}

	public void detachSink()
	{
		getSink().removeInStream(this);
	}

	public ProcessUnit getSource()
	{
		return (ProcessUnit)getParent();
	}

	public ProcessUnit getSink()
	{
		return outPU;
	}

	public void setSource(ProcessUnit inPU)
	{
		setParent(inPU);
	}

	public void setSink(ProcessUnit outPU)
	{
		this.outPU = outPU;
		
		if(null == outPU)
		{
			return;
		}
		
		//was not a terminal, now is so.
		if(!isTerminalStream() && outPU instanceof ITerminal)
		{
			/*
			 * Terminal units do not have incoming streams decorated with arrows
			 * or any other target decorator 
			 */
			setTerminalStream(true);
			firePropertyChange(P_FEEDBACK_STATE, false, true);
		}
		else if(isTerminalStream())
		{
			/*
			 * Process units have incoming streams decorated if one is set for the object.
			 */
			setTerminalStream(false);
			firePropertyChange(P_FEEDBACK_STATE, true, false);
		}
	}

	public void addBendpoint(int index, Point point)
	{
		bendpoints.add(index, point);
		firePropertyChange(P_BEND_POINT, null, null);
	}

	public List<Point> getBendpoints()
	{
		return bendpoints;
	}

	public void removeBendpoint(int index)
	{
		bendpoints.remove(index);
		firePropertyChange(P_BEND_POINT, null, null);
	}

	public void replaceBendpoint(int index, Point point)
	{
		bendpoints.set(index, point);
		firePropertyChange(P_BEND_POINT, null, null);
	}
}
